﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Linq.Expressions;
using Microsoft.CSharp.RuntimeBinder;
using System.Diagnostics;

namespace UIT.Markup
{
    public class BindingFragment : INotifyPropertyChanged, IDisposable
    {
        public BindingFragment(bool registerToPropertyChange)
        {
            this.registerToPropertyChange = registerToPropertyChange;
        }

        Delegate getter;
        Delegate setter;

        private string member;

        public string Member
        {
            get { return member; }
            set
            {
                member = value;
                getter = null;
                setter = null;
                OnPropertyChanged("Member");
            }
        }



        public object GetValue(object target)
        {
            if (target == null)
                return null;
            if (getter == null)
            {
                Expression body;
                ParameterExpression parameter = Expression.Parameter(target.GetType(), "context");
                if (string.IsNullOrEmpty(Member))
                    body = parameter;
                else
                    body = Expression.Dynamic(Binder.GetMember(CSharpBinderFlags.None, Member, typeof(Binding), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }), typeof(object), parameter);
                if (registerToPropertyChange)
                {
                    INotifyPropertyChanged notifyTarget = target as INotifyPropertyChanged;
                    if (notifyTarget != null)
                    {
                        notifyTarget.PropertyChanged += new PropertyChangedEventHandler(notifyTarget_PropertyChanged);
                    }
                }
                getter = Expression.Lambda(body, parameter).Compile();
            }
            try
            {
                return getter.DynamicInvoke(target);
            }
            catch (RuntimeBinderException ex)
            {
                Binding.Trace.TraceEvent(TraceEventType.Error, 1, ex.Message);
            }
            catch (Exception ex)
            {
                Binding.Trace.TraceEvent(TraceEventType.Error, 2, ex.Message);
            }
            return null;
        }

        public void SetValue(object target, object value)
        {
            Expression body;
            if (target == null || string.IsNullOrEmpty(Member))
                return;
            if (setter == null)
            {
                ParameterExpression targetParameter = Expression.Parameter(target.GetType(), "context");
                ParameterExpression valueParameter = Expression.Parameter(typeof(object), "value");

                body = Expression.Dynamic(Binder.SetMember(CSharpBinderFlags.InvokeSpecialName, Member, typeof(Binding), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant, "value") }), typeof(object), targetParameter, valueParameter);
                //INotifyPropertyChanged notifyTarget = target as INotifyPropertyChanged;
                //if (notifyTarget != null)
                //{
                //    notifyTarget.PropertyChanged += notifyTarget_PropertyChanged;
                //}
                setter = Expression.Lambda(body, targetParameter, valueParameter).Compile();
            }
            if (doNotNotifyChange)
                return;
            doNotNotifyChange = true;
            try
            {
                setter.DynamicInvoke(target, value);
            }
            catch (Exception ex)
            {
                //if (ex.InnerException is RuntimeBinderException)
                //    throw ex.InnerException;

                Console.WriteLine(ex);
            }
            doNotNotifyChange = false;
        }

        void notifyTarget_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!doNotNotifyChange && e.PropertyName == Member)
            {
                doNotNotifyChange = true;
                OnPropertyChanged("Value");
                doNotNotifyChange = false;
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Prevents from infinite loop
        /// </summary>
        private bool doNotNotifyChange;
        private bool registerToPropertyChange;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region IDisposable Members

        public void Dispose(object target)
        {
            Dispose();
            INotifyPropertyChanged notifyTarget = target as INotifyPropertyChanged;
            if (notifyTarget != null)
                notifyTarget.PropertyChanged -= notifyTarget_PropertyChanged;
        }


        public void Dispose()
        {
            PropertyChanged = null;
            getter = null;
            setter = null;
        }

        #endregion
    }
}
